/*******************************************************************************
* Copyright (c) 2011 Stephan Schwiebert. All rights reserved. This program and
* the accompanying materials are made available under the terms of the Eclipse
* Public License v1.0 which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
* <p/>
* Contributors: Stephan Schwiebert - initial API and implementation
*******************************************************************************/
package org.eclipse.zest.cloudio;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.jface.viewers.ContentViewer;
import org.eclipse.jface.viewers.IBaseLabelProvider;
import org.eclipse.jface.viewers.IContentProvider;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.IStructuredContentProvider;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.MouseListener;
import org.eclipse.swt.events.MouseTrackListener;
import org.eclipse.swt.events.MouseWheelListener;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.widgets.Control;
import org.eclipse.zest.cloudio.layout.ILayouter;
/**
* A model-based adapter for a {@link TagCloud}.
* @author sschwieb
*/
public class TagCloudViewer extends ContentViewer {
private TagCloud cloud;
private Set<Word> selection = new HashSet<Word>();
private Map<Object, Word> objectMap = new HashMap<Object, Word>();
private int maxWords = 300;
private IProgressMonitor monitor;
/**
* Create a new TagCloudViewer for the given {@link TagCloud},
* which must not be <code>null</code>.
* @param cloud
*/
public TagCloudViewer(TagCloud cloud) {
Assert.isLegal(cloud != null, "TagCloud must not be null!");
Assert.isLegal(!cloud.isDisposed(), "TagCloud must not be disposed!");
this.cloud = cloud;
initListeners();
}
/**
* Initialize the default tag cloud listeners.
* Can be overridden to modify the behaviour of the viewer.
*/
protected void initListeners() {
initSelectionListener();
initMouseWheelListener();
initToolTipSupport();
}
/**
* Initialize tool tip support when the cursor
* hovers a word.
*/
protected void initToolTipSupport() {
cloud.addMouseTrackListener(new MouseTrackListener() {
@Override
public void mouseHover(MouseEvent e) {}
@Override
public void mouseExit(MouseEvent e) {
cloud.setToolTipText(null);
}
@Override
public void mouseEnter(MouseEvent e) {
Word word = (Word) e.data;
ICloudLabelProvider labelProvider = (ICloudLabelProvider) getLabelProvider();
cloud.setToolTipText(labelProvider.getToolTip(word.data));
}
});
}
/**
* Initialize the mouse wheel listener to support
* zooming in and out.
*/
protected void initMouseWheelListener() {
cloud.addMouseWheelListener(new MouseWheelListener() {
@Override
public void mouseScrolled(MouseEvent e) {
if(e.count > 0) {
cloud.zoomIn();
} else {
cloud.zoomOut();
}
}
});
}
/**
* Initialize default selection behaviour: Words can
* be selected by mouse click, and selection listeners
* are notified when the selection changed.
*/
protected void initSelectionListener() {
cloud.addMouseListener(new MouseListener() {
@Override
public void mouseUp(MouseEvent e) {
Word word = (Word) e.data;
if(word == null) return;
boolean remove = selection.remove(word);
if(!remove) selection.add(word);
cloud.setSelection(selection);
}
@Override
public void mouseDown(MouseEvent e) {
}
@Override
public void mouseDoubleClick(MouseEvent e) {
}
});
cloud.addSelectionListener(new SelectionListener() {
@Override
public void widgetSelected(SelectionEvent e) {
List<Object> data = new ArrayList<Object>();
@SuppressWarnings("unchecked")
Set<Word> selected = (Set<Word>) e.data;
for (Word word : selected) {
if(word.data != null) {
data.add(word.data);
}
}
StructuredSelection selection = new StructuredSelection(data);
fireSelectionChanged(new SelectionChangedEvent(TagCloudViewer.this, selection));
}
@Override
public void widgetDefaultSelected(SelectionEvent e) {}
});
}
/*
* (non-Javadoc)
* @see org.eclipse.jface.viewers.Viewer#getControl()
*/
@Override
public Control getControl() {
return getCloud();
}
/**
* Returns the currently selected elements, as an
* {@link IStructuredSelection}. Returns an empty
* selection if no elements are selected.
*/
@Override
public ISelection getSelection() {
List<Object> elements = new ArrayList<Object>();
for (Word word : selection) {
elements.add(word.data);
}
return new StructuredSelection(elements);
}
@Override
public void refresh() {
}
/*
* (non-Javadoc)
* @see org.eclipse.jface.viewers.Viewer#setSelection(org.eclipse.jface.viewers.ISelection, boolean)
*/
@Override
public void setSelection(ISelection selection, boolean reveal) {
this.selection.clear();
IStructuredSelection sel = (IStructuredSelection) selection;
Iterator<?> iterator = sel.iterator();
while(iterator.hasNext()) {
Object next = iterator.next();
Word word = objectMap.get(next);
if(word != null) {
this.selection.add(word);
}
}
cloud.setSelection(this.selection);
}
/**
* Resets the {@link TagCloud}. If <code>recalc</code> is
* <code>true</code>, the displayed elements will be updated
* with the values provided by used {@link ICloudLabelProvider}.
* Otherwise, the cloud will only be re-layouted, keeping fonts,
* colors and angles untouched.
* @param monitor
* @param recalc
*/
public void reset(IProgressMonitor monitor, boolean recalc) {
cloud.layoutCloud(monitor, recalc);
}
/**
* Returns the {@link TagCloud} managed by this viewer.
* @return
*/
public TagCloud getCloud() {
return cloud;
}
/**
* Sets the label provider of this viewer, which must be an
* {@link ICloudLabelProvider}.
*/
@Override
public void setLabelProvider(IBaseLabelProvider labelProvider) {
super.setLabelProvider(labelProvider);
Assert.isLegal(labelProvider instanceof ICloudLabelProvider);
}
/**
* Sets the content provider of this viewer, which must be
* an {@link IStructuredContentProvider}.
*/
@Override
public void setContentProvider(IContentProvider contentProvider) {
Assert.isLegal(contentProvider instanceof IStructuredContentProvider);
super.setContentProvider(contentProvider);
}
/*
* (non-Javadoc)
* @see org.eclipse.jface.viewers.Viewer#inputChanged(java.lang.Object, java.lang.Object)
*/
@Override
protected void inputChanged(Object input, Object oldInput) {
selection.clear();
objectMap.clear();
IStructuredContentProvider contentProvider = (IStructuredContentProvider) getContentProvider();
Object[] elements = contentProvider.getElements(input);
List<Word> words = new ArrayList<Word>();
ICloudLabelProvider labelProvider = (ICloudLabelProvider) getLabelProvider();
short i = 0;
for (Object element : elements) {
Word word = new Word(labelProvider.getLabel(element));
word.setColor(labelProvider.getColor(element));
word.weight = labelProvider.getWeight(element);
word.setFontData(labelProvider.getFontData(element));
word.angle = labelProvider.getAngle(element);
word.data = element;
Assert.isLegal(word.string != null, "Labelprovider must return a String for each element");
Assert.isLegal(word.getColor() != null, "Labelprovider must return a Color for each element");
Assert.isLegal(word.getFontData() != null, "Labelprovider must return a FontData for each element");
Assert.isLegal(word.weight >= 0, "Labelprovider must return a weight between 0 and 1 (inclusive), but value was " + word.weight);
Assert.isLegal(word.weight <= 1, "Labelprovider must return a weight between 0 and 1 (inclusive), but value was " + word.weight);
Assert.isLegal(word.angle >= -90, "Angle of an element must be between -90 and +90 (inclusive), but was " + word.angle);
Assert.isLegal(word.angle <= 90, "Angle of an element must be between -90 and +90 (inclusive), but was " + word.angle);
words.add(word);
i++;
word.id = i;
objectMap.put(element, word);
if(i == maxWords) break;
}
selection.clear();
if(monitor != null) {
monitor.subTask("Layouting...");
}
cloud.setWords(words, monitor);
}
/**
* Sets the maximum number of elements which will be
* displayed by the cloud. Note that there is no guarantee
* that this amount of elements will actually be displayed,
* as this depends on additional factors.
* @return
*/
public void setMaxWords(int words) {
this.maxWords = words;
}
/**
* Calls {@link TagCloud#zoomFit()} to scale the cloud such
* that it fits the current visible area.
*/
public void zoomFit() {
cloud.zoomFit();
}
/**
* Zooms in
*/
public void zoomIn() {
cloud.zoomIn();
}
/**
* Zooms out
*/
public void zoomOut() {
cloud.zoomOut();
}
/**
* Resets the zoom to 100%
*/
public void zoomReset() {
cloud.zoomReset();
}
public void setBoost(int boost) {
cloud.setBoost(boost);
}
/**
* Returns the maximum number of elements which will be
* displayed by the cloud. Note that there is no guarantee
* that this amount of elements will actually be displayed,
* as this depends on additional factors.
* @return
*/
public int getMaxWords() {
return maxWords;
}
/**
* Same as {@link TagCloudViewer#setInput(Object)}, but with
* an {@link IProgressMonitor} to provide feedback during the
* layout phase.
* @param input
* @param progressMonitor
*/
public void setInput(Object input, IProgressMonitor progressMonitor) {
this.monitor = progressMonitor;
super.setInput(input);
this.monitor = null;
}
public void setBoostFactor(float boostFactor) {
cloud.setBoostFactor(boostFactor);
}
public void setLayouter(ILayouter layouter) {
cloud.setLayouter(layouter);
}
public ILayouter getLayouter() {
return cloud.getLayouter();
}
}